package com.fly.base;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.base.Strings;
import org.apache.commons.beanutils.BeanMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Condition;
import tk.mybatis.mapper.entity.Example;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.fly.base.DataTables.Between;

/**
 * @author frinder
 * @date 2018/5/19
 * @className AbstractService
 * @package com.fly.base
 * @Description: ${todo}
 */
@Transactional(rollbackFor = {Exception.class, ServiceException.class})
public abstract class AbstractService<T> implements Service<T> {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    protected Mapper<T> mapper;

    /**
     * 当前泛型真实类型的Class
     */
    private Class<T> modelClass;

    public AbstractService() {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        modelClass = (Class<T>) pt.getActualTypeArguments()[0];
    }

    @Override
    public int save(T model) {
        try {
            Method createTime = modelClass.getMethod("setCreateTime", Date.class);
            createTime.setAccessible(true);
            createTime.invoke(model, new Date());
        } catch (Exception e) {
        }
        int result = mapper.insertUseGeneratedKeys(model);
        if (result <= 0) {
            throw new ServiceException(ServiceException.Code.SAVE_FAIL);
        }
        return result;
    }

    @Override
    public int save(List<T> models) {
        try {
            Method createTime = modelClass.getMethod("setCreateTime", Date.class);
            createTime.setAccessible(true);
            Method updateTime = modelClass.getMethod("setUpdateTime", Date.class);
            updateTime.setAccessible(true);
            Method isDelete = modelClass.getMethod("setIsDeleted", Boolean.class);
            isDelete.setAccessible(true);
            models.stream().forEach(m -> {
                try {
                    createTime.invoke(m, new Date());
                    updateTime.invoke(m, new Date());
                    isDelete.invoke(m, false);
                } catch (Exception e) {
                }
            });
        } catch (Exception e) {
        }
        int result = mapper.insertList(models);
        if (result <= 0) {
            throw new ServiceException(ServiceException.Code.SAVE_FAIL);
        }
        return result;
    }

    @Override
    public int deleteById(Long id) {
        int result = mapper.deleteByPrimaryKey(id);
        if (result <= 0) {
            throw new ServiceException(ServiceException.Code.DELETE_FAIL);
        }
        return result;
    }

    @Override
    public int deleteByIds(String ids) {
        int result = mapper.deleteByIds(ids);
        if (result <= 0) {
            throw new ServiceException(ServiceException.Code.DELETE_FAIL);
        }
        return result;
    }

    @Override
    public int update(T model) {
        int result = mapper.updateByPrimaryKeySelective(model);
        if (result <= 0) {
            throw new ServiceException(ServiceException.Code.UPDATE_FAIL);
        }
        return result;
    }

    @Override
    public T findById(Long id) {
        return mapper.selectByPrimaryKey(id);
    }

    @Override
    public T findBy(String property, Object value) {
        try {
            T model = modelClass.newInstance();
            Field field = modelClass.getDeclaredField(property);
            field.setAccessible(true);
            field.set(model, value);
            return mapper.selectOne(model);
        } catch (Exception e) {
            throw new ServiceException(ServiceException.Code.MORE_THAN_ONE);
        }
    }

    @Override
    public List<T> findByModel(T model) {
        return mapper.select(model);
    }

    @Override
    public List<T> findByIds(String ids) {
        return mapper.selectByIds(ids);
    }

    @Override
    public List<T> findByCondition(Condition condition) {
        return mapper.selectByCondition(condition);
    }

    @Override
    public List<T> findByModelWrapper(ModelWrapper<T> wrapper) {
        Condition condition = createCondition(wrapper);
        return mapper.selectByCondition(condition);
    }

    @Override
    public List<T> findAll() {
        return mapper.selectAll();
    }

    @Override
    public DataTables<T> page(DataTables<T> dataTables) {
        Condition condition = createCondition(dataTables);
        List<T> list = mapper.selectByCondition(condition);
        PageInfo pageInfo = new PageInfo(list);
        dataTables.setRecordsTotal(pageInfo.getTotal());
        dataTables.setRecordsFiltered(pageInfo.getTotal());
        dataTables.setData(list);
        return dataTables;
    }

    /**
     * @param wrapper
     * @return
     */
    private Condition createCondition(ModelWrapper<T> wrapper) {
        Condition condition = new Condition(modelClass);
        try {
            Object target = wrapper.getTarget();
            if (null != target) {
                Example.Criteria criteria = condition.createCriteria();
                Map<Object, Object> fields = new BeanMap(target);
                String key;
                Object v;
                for (Map.Entry<Object, Object> entry : fields.entrySet()) {
                    key = String.valueOf(entry.getKey());
                    if ("class".equalsIgnoreCase(key)) {
                        continue;
                    }
                    v = entry.getValue();
                    if (wrapper.getLikeFields().contains(key)) {
                        if (null == v) {
                            continue;
                        }
                        criteria.andLike(key, String.format("%s%%", v));
                    } else if (wrapper.getEqualFields().contains(key)) {
                        if (null == v) {
                            continue;
                        }
                        criteria.andEqualTo(key, v);
                    } else if (wrapper.getNotEqualFields().contains(key)) {
                        if (null == v) {
                            continue;
                        }
                        criteria.andNotEqualTo(key, v);
                    } else if (wrapper.getInFields().containsKey(key)) {
                        criteria.andIn(key, wrapper.getInFields().get(key));
                    } else if (wrapper.getNotInFields().containsKey(key)) {
                        criteria.andNotIn(key, wrapper.getNotInFields().get(key));
                    } else if (wrapper.getGreaterThanFields().containsKey(key)) {
                        criteria.andGreaterThan(key, wrapper.getGreaterThanFields().get(key));
                    } else if (wrapper.getGreaterThanOrEqualFields().containsKey(key)) {
                        criteria.andGreaterThanOrEqualTo(key, wrapper.getGreaterThanOrEqualFields().get(key));
                    } else if (wrapper.getLessThanFields().containsKey(key)) {
                        criteria.andLessThan(key, wrapper.getLessThanFields().get(key));
                    } else if (wrapper.getLessThanOrEqualFields().containsKey(key)) {
                        criteria.andLessThanOrEqualTo(key, wrapper.getLessThanOrEqualFields().get(key));
                    } else {
                        if (null == v) {
                            continue;
                        }
                        if (v instanceof String) {
                            if (!Strings.isNullOrEmpty(v.toString())) {
                                criteria.andLike(key, String.format("%s%%", v));
                            }
                        } else if (v instanceof Date) {
                        } else {
                            criteria.andEqualTo(key, v);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return condition;
    }

    /**
     * @param page
     * @return
     */
    private Condition createCondition(DataTables<T> page) {
        PageHelper.startPage(page.getPage(), page.getLength());
        Condition condition = new Condition(modelClass);
        try {
            Example.Criteria criteria = condition.createCriteria();
            if (null != page.getCreateTimeFrom()) {
                criteria.andGreaterThanOrEqualTo("createTime", page.getCreateTimeFrom());
            }
            if (null != page.getCreateTimeTo()) {
                criteria.andLessThanOrEqualTo("createTime", page.getCreateTimeTo());
            }
            Object target = page.getObject(modelClass);
            if (null != target) {
                Map<Object, Object> fields = new BeanMap(target);
                String key;
                Object v;
                Map<String, Between> betweenMap = page.getBetweenList().stream().collect(Collectors.toMap(Between::getField, s -> s));
                for (Map.Entry<Object, Object> entry : fields.entrySet()) {
                    key = String.valueOf(entry.getKey());
                    if ("class".equalsIgnoreCase(key)) {
                        continue;
                    }
                    v = entry.getValue();
                    if (page.getEqualFields().contains(key)) {
                        if (null == v) {
                            continue;
                        }
                        criteria.andEqualTo(key, v);
                    } else if (page.getNotEqualFields().contains(key)) {
                        if (null == v) {
                            continue;
                        }
                        criteria.andNotEqualTo(key, v);
                    } else if (page.getInFields().containsKey(key)) {
                        criteria.andIn(key, page.getInFields().get(key));
                    } else if (page.getNotInFields().containsKey(key)) {
                        criteria.andNotIn(key, page.getNotInFields().get(key));
                    } else if (page.getGreaterThanFields().containsKey(key)) {
                        criteria.andGreaterThan(key, page.getGreaterThanFields().get(key));
                    } else if (page.getGreaterThanOrEqualFields().containsKey(key)) {
                        criteria.andGreaterThanOrEqualTo(key, page.getGreaterThanOrEqualFields().get(key));
                    } else if (page.getLessThanFields().containsKey(key)) {
                        criteria.andLessThan(key, page.getLessThanFields().get(key));
                    } else if (page.getLessThanOrEqualFields().containsKey(key)) {
                        criteria.andLessThanOrEqualTo(key, page.getLessThanOrEqualFields().get(key));
                    } else if (betweenMap.containsKey(key)) {
                        criteria.andBetween(key, betweenMap.get(key).getMinValue(), betweenMap.get(key).getMaxValue());
                    } else if (page.getSearchFields().contains(key) && !Strings.isNullOrEmpty(page.getSearchText())) {
                        criteria.orLike(key, String.format("%s%%", page.getSearchText()));
                    } else {
                        if (null == v) {
                            continue;
                        }
                        if (v instanceof String) {
                            if (!Strings.isNullOrEmpty(v.toString())) {
                                criteria.andLike(key, String.format("%s%%", v));
                            }
                        } else if (v instanceof Date) {
                        } else {
                            criteria.andEqualTo(key, v);
                        }
                    }
                }
            }
            page.getOrderBys().stream().forEach(order -> {
                switch (order.getStrategy()) {
                    case DESC:
                        condition.orderBy(order.getField()).desc();
                        break;
                    case ASC:
                        condition.orderBy(order.getField()).asc();
                        break;
                    default:
                        break;
                }
            });
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return condition;
    }

}
